#include "RoverOily_Common.hlsl"

Texture1D<float4> IniParams : register(t120);
#define roverOilyMode IniParams[255].z

float3 RoverRotateAroundZDegrees(float3 v, float degrees)
{
    float radians = degrees * 0.01745329252;
    float s = sin(radians);
    float c = cos(radians);
    return float3(
        v.x * c - v.y * s,
        v.x * s + v.y * c,
        v.z);
}

// Large, soft UV-space oil regions. These are intentionally low frequency so
// the surface reads as patches of wet/oily film instead of uniform bright skin.
float RoverOilyLargePatchMask(float2 uv)
{
    const float TAU = 6.28318530718;

    float f0 = sin((uv.x * 1.18 + uv.y * 0.24) * TAU + 0.35);
    float f1 = sin((uv.y * 0.94 - uv.x * 0.21) * TAU + 2.30);
    float f2 = sin((uv.x * 0.58 - uv.y * 0.72) * TAU + 4.05);

    float field = f0 * 0.46 + f1 * 0.34 + f2 * 0.20;
    return smoothstep(-0.34, 0.42, field);
}

void main(
  linear centroid float4 v0 : TEXCOORD10,
  linear centroid float4 v1 : TEXCOORD11,
  float2 v2 : TEXCOORD0,
  float2 v3 : TEXCOORD1,
  float2 v4 : TEXCOORD2,
  float2 v5 : TEXCOORD3,
  float2 v6 : TEXCOORD4,
  float4 v7 : VELOCITY_PREV_POS0,
  float4 v8 : SV_Position0,
  uint v9 : SV_IsFrontFace0,
  out float4 o0 : SV_Target0,
  out float4 o3 : SV_Target3)
{
    float3 worldPosition = RoverOilyWorldPosition(v8);
    float3 V = normalize(-worldPosition);
    float3 L = RoverOilyLightDirection(worldPosition, v3, v6.x);
    float3 H = normalize(V + L);

    float3 geometricN = normalize(v1.xyz);
    float3 detailN = RoverOilyWorldNormal(v0, v1, v2);

    float detailBlend;
    float patchFloor;
    float fresnelSoftStrength;
    float fresnelHardStrength;
    float halfBroadStrength;
    float halfTightStrength;
    float pinpointStrength;
    float crossGlintStrength;
    float lightGlintStrength;
    float rt0Scale;
    float rt3Scale;

    if (roverOilyMode < 1.5)
    {
        // Soft reflective skin film. Very seam safe and intentionally subtle.
        detailBlend = 0.04;
        patchFloor = 0.68;
        fresnelSoftStrength = 0.18;
        fresnelHardStrength = 0.10;
        halfBroadStrength = 0.10;
        halfTightStrength = 0.10;
        pinpointStrength = 0.06;
        crossGlintStrength = 0.08;
        lightGlintStrength = 0.08;
        rt0Scale = 0.18;
        rt3Scale = 0.82;
    }
    else if (roverOilyMode < 2.5)
    {
        // Recommended mode: much less like lit skin, much more like a wet/oily
        // reflective film. Broad front-facing brightening is removed; energy is
        // concentrated into grazing Fresnel and sharper clearcoat glints.
        detailBlend = 0.08;
        patchFloor = 0.50;
        fresnelSoftStrength = 0.24;
        fresnelHardStrength = 0.22;
        halfBroadStrength = 0.16;
        halfTightStrength = 0.20;
        pinpointStrength = 0.15;
        crossGlintStrength = 0.18;
        lightGlintStrength = 0.12;
        rt0Scale = 0.24;
        rt3Scale = 1.12;
    }
    else
    {
        // Strongest wet-film mode: large overlapping wet/oil regions with the
        // clearest water-film character and the least uniform "bright skin" look.
        detailBlend = 0.06;
        patchFloor = 0.26;
        fresnelSoftStrength = 0.21;
        fresnelHardStrength = 0.28;
        halfBroadStrength = 0.20;
        halfTightStrength = 0.18;
        pinpointStrength = 0.12;
        crossGlintStrength = 0.15;
        lightGlintStrength = 0.10;
        rt0Scale = 0.26;
        rt3Scale = 1.22;
    }

    float patchMask = lerp(patchFloor, 1.0, RoverOilyLargePatchMask(v2));

    // Keep the seam fix: broad film and Fresnel remain based on the continuous
    // mesh normal. Only a restrained part of the tighter glint uses detailN.
    float3 specularN = normalize(lerp(geometricN, detailN, detailBlend));

    float geometricNdotV = saturate(dot(geometricN, V));
    float specularNdotH = saturate(dot(specularN, H));
    float specularNdotL = saturate(dot(specularN, L));

    float grazing = 1.0 - geometricNdotV;
    float fresnelSoft = pow(grazing, 1.7);
    float fresnelHard = pow(grazing, 4.6);

    float halfBroad = pow(max(specularNdotH, 0.0001), 18.0);
    float halfTight = pow(max(specularNdotH, 0.0001), 64.0);
    float halfPinpoint = pow(max(specularNdotH, 0.0001), 180.0);

    float3 rotatedVPlus = normalize(RoverRotateAroundZDegrees(V, 45.0));
    float3 rotatedVMinus = normalize(RoverRotateAroundZDegrees(V, -45.0));
    float rotatedDotPlus = saturate(dot(specularN, rotatedVPlus));
    float rotatedDotMinus = saturate(dot(specularN, rotatedVMinus));
    float crossGlint =
        pow(max(rotatedDotPlus, 0.0001), 180.0)
        + pow(max(rotatedDotMinus, 0.0001), 180.0);

    float lightGlint = pow(max(specularNdotL, 0.0001), 7.0) * halfBroad;

    float filmSignal =
        fresnelSoft * fresnelSoftStrength
        + fresnelHard * fresnelHardStrength
        + halfBroad * halfBroadStrength
        + halfTight * halfTightStrength
        + halfPinpoint * pinpointStrength
        + crossGlint * crossGlintStrength
        + lightGlint * lightGlintStrength;

    filmSignal *= RO_COMPONENT_STRENGTH * patchMask;

    float3 tint = RO_COMPONENT_TINT;

    // RT0 carries only a restrained base film so the skin does not read as
    // simply brighter. RT3 receives most of the crisp watery/oily reflection.
    float3 rt0Reflection = tint * filmSignal * (0.60 * fresnelSoft + 0.40 * halfBroad) * rt0Scale;
    float3 rt3Reflection = tint * filmSignal * (0.55 + 0.45 * saturate(fresnelHard + halfTight + crossGlint)) * rt3Scale;

    o0 = float4(max(rt0Reflection, 0.0), 0.0);
    o3 = float4(max(rt3Reflection, 0.0), 0.0);
}
